AgentDeck · android · ui/eink
The design canvas (E-ink Dashboard.html) is the spec.
This page is the real-code reflection: every
decision from the canvas mapped to a concrete file in
android/app/src/main/kotlin/dev/agentdeck/, ready to
drop in.
RefreshMode.FULL_ONCE variant — semantic "one clean GC16 flash on appearance, then stay quiet". Maps to the same vendor call as FULL but separates intent.Zone enum captures mode + debounce per zone so call sites stop hardcoding waveform constants.@Preview and screenshot tests.EinkRefreshHelper.requestFullRefresh via SystemProperties.set("sys.eink.update", "GC16"). Falls through to view.invalidate() on unsupported devices.EinkRefreshZone with FULL_ONCE policy. All zone call sites (5 in landscape, 3 in portrait) now read mode + debounce from Zone.CHROME / ATTENTION / CONTEXT_FAST / STATUS_SLOW / TIMELINE instead of inline literals.Before: every zone hardcoded its mode/debounce inline at the call site
(RefreshMode.A2, 200). Six zones × landscape + portrait = 11
duplicates that drifted over time. After: every call site reads from
Zone.
| Zone | Mode | Debounce | Trigger |
|---|---|---|---|
| CHROME | A2 | 200 ms | session list / agent states |
| TERRARIUM | animated | per-frame | creature animation loop |
| ATTENTION | FULL_ONCE | 80 ms | awaiting state appears |
| CONTEXT_FAST | A2 | 200 ms | tool / option changes |
| STATUS_SLOW | DU | 2000 ms | usage / health / models |
| TIMELINE | A2 | 300 ms | append-only entry count |
In the old code the featuredAttention != null branch lived
inside the Context+Status EinkRefreshZone on
RefreshMode.A2. Two problems:
AWAITING_PERMISSION never forced a clean GC16 flash — the prompt just appeared in whatever waveform mode the zone happened to be in.// after — EinkMonitorScreen.kt
if (featuredAttention != null) {
EinkRefreshZone(
mode = Zone.ATTENTION.mode, // FULL_ONCE
debounceMs = Zone.ATTENTION.debounceMs, // 80 ms
triggerKey = featuredAttention,
) { /* permission / option panel */ }
} else if (hasContext || showStatusPanel) {
// CONTEXT_FAST / STATUS_SLOW — never sees the prompt
}
The proposal called for a brand-new EpdRefreshController
interface. Reading EinkRefreshHelper showed the helper IS
the port — Rockchip + Onyx already covered. Minimal change instead:
add a third fallback for Kobo / Tolino, document the chain.
fun requestFullRefresh(view: View) {
// 1. Rockchip RK35xx (Crema S, Xiaomi Reader)
if (tryRockchipRefresh(view, RK_EPD_FULL_GC16, sendFullFrame = !einkColorEnabled)) return
// 2. Onyx Boox (Qualcomm)
try { onyxClass.getMethod("requestScreenUpdate", View::class.java).invoke(null, view); return }
catch (_: Exception) {}
+ // 3. Kobo / Tolino / KOReader — system property bridge
+ if (tryKoboRefresh(view, koboMode = "GC16")) return
// 4. Fallback: standard invalidate
view.invalidate()
}
The design canvas illustrated five session topologies; they now exist
as EinkScenarios fixtures consumable from
@Preview parameters and screenshot tests.
RefreshMode.FULL_ONCE in EinkRefreshZone.ktEinkZonePolicy.kt with the Zone enumEinkScenarios.kt fixturesEinkRefreshHelper.requestFullRefreshZone.* into EinkMonitorScreenRefreshMode.A2, 200 with Zone.CHROME.mode, Zone.CHROME.debounceMsfeaturedAttention != null branch out of Context+Status zoneEinkRefreshZone(Zone.ATTENTION.mode, ...)| Path | Kind | Risk |
|---|---|---|
ui/eink/EinkRefreshZone.kt | add enum variant | LOW |
ui/eink/EinkZonePolicy.kt | new file | LOW |
ui/eink/EinkScenarios.kt | new file (test only) | LOW |
terrarium/renderer/EinkRenderer.kt | add fallback driver | LOW |
ui/screen/EinkMonitorScreen.kt | extract ATTENTION zone | MEDIUM |
Drop-in files live under patches/ in this project.
patches/new/ contains the three new files; the other
three are full pre-edited copies of the originals so you can diff
them against your working tree.